<?php
/**
 * Nuwani PHP IRC Bot Framework
 * Copyright (c) 2006-2010 The Nuwani Project
 *
 * Nuwani is a framework for IRC Bots built using PHP. Nuwani speeds up bot 
 * development by handling basic tasks as connection- and bot management, timers
 * and module managing. Features for your bot can easily be added by creating
 * your own modules, which will receive callbacks from the framework.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @copyright Copyright (c) 2006-2011 The Nuwani Project
 * @author Peter Beverloo <peter@lvp-media.com>
 * @author Dik Grapendaal <dik.grapendaal@gmail.com>
 * @see http://nuwani.googlecode.com
 */

//namespace Nuwani;

/**
 * Uno Nuwani Module
 * 
 * @copyright Copyright (c) 2011 The Nuwani Project, http://nuwani.googlecode.com/
 * @author Dik Grapendaal <dik.grapendaal@gmail.com>
 * @version $Id: IrcInput.php 163 2013-07-12 22:17:59Z dik.grapendaal $
 * @package Modules
 */

class IrcInput extends UnoInput
{
        /**
         * The color map for the display of the colors of the cards. These will
         * be used to parse the commands given by players, so that the engine
         * can make sense of the input.
         * 
         * @var array
         */
        
        private static $sColorMap = array
        (
                'b'     => UnoEngine :: COLOR_BLUE,
                'g'     => UnoEngine :: COLOR_GREEN,
                'r'     => UnoEngine :: COLOR_RED,
                'y'     => UnoEngine :: COLOR_YELLOW,
                'w'     => UnoEngine :: COLOR_WILDCARD
        );
        
        /**
         * The number map for the numbers of the cards.
         * 
         * @var array
         */
        
        private static $sNumberMap = array
        (
                ''      => UnoEngine :: CARD_NONE,
                '0'     => UnoEngine :: CARD_0,
                '1'     => UnoEngine :: CARD_1,
                '2'     => UnoEngine :: CARD_2,
                '3'     => UnoEngine :: CARD_3,
                '4'     => UnoEngine :: CARD_4,
                '5'     => UnoEngine :: CARD_5,
                '6'     => UnoEngine :: CARD_6,
                '7'     => UnoEngine :: CARD_7,
                '8'     => UnoEngine :: CARD_8,
                '9'     => UnoEngine :: CARD_9,
                'd2'    => UnoEngine :: CARD_DRAW_2,
                'd4'    => UnoEngine :: CARD_DRAW_4,
                'r'     => UnoEngine :: CARD_REVERSE,
                's'     => UnoEngine :: CARD_SKIP
        );
        
        /**
         * The symbol in front of commands, to indicate that the user actually
         * wanted to execute a command. Cached here from the Commands module, so
         * that we are consistent with the rest of the commands.
         * 
         * @var string
         */
        
        private $mCommandPrefix;
        
        /**
         * If a game is running, this property stores the IRC channel the game
         * is currently running in. Null otherwise.
         * 
         * @var string
         */
        
        private $mActiveChannel;
        
        /**
         * Returns the currently set command prefix.
         * 
         * @return string
         */
        
        public function getCommandPrefix ()
        {
                return $this -> mCommandPrefix;
        }
        
        /**
         * Sets the command prefix as used by the Commands module.
         * 
         * @param string $prefix The prefix to set.
         */
        
        public function setCommandPrefix ($prefix)
        {
                $this -> mCommandPrefix = $prefix;
        }
        
        /**
         * Tries to make sense of commands sent by users on IRC and calls the
         * appropriate methods on the UnoEngine, which in turn will do stuff.
         * 
         * @param string $channel The IRC channel the line appeared in.
         * @param string $nickname The person who typed this line.
         * @param string $line The line received from IRC.
         */
        
        public function handleIrcCommand ($channel, $nickname, $line)
        {
                $command = $line;
                $parameters = null;
                
                if (strpos ($line, ' ') !== false)
                {
                        list ($command, $parameters) = explode (' ', $line, 2);
                }
                
                // Remove the command prefix if it's there.
                if (substr ($command, 0, strlen ($this -> mCommandPrefix)) == $this -> mCommandPrefix)
                {
                        $command = substr ($command, strlen ($this -> mCommandPrefix));
                        
                        // Certain commands we don't want without a prefix.
                        if ($command == UnoStrings :: COMMAND_STOP
                         || $command == UnoStrings :: COMMAND_SHUFFLE
                         || $command == UnoStrings :: COMMAND_STATUS)
                        {
                                $command = $this -> mCommandPrefix . $command;
                        }
                }
                
                switch ($this -> mEngine -> getState ())
                {
                        case UnoEngine :: STATE_IDLE:
                        {
                                $this -> handleIdleCommand ($channel, $nickname, $command, $parameters);
                                break;
                        }
                        
                        case UnoEngine :: STATE_PENDING:
                        {
                                $this -> handlePendingCommand ($channel, $nickname, $command, $parameters);
                                break;
                        }
                        
                        case UnoEngine :: STATE_PLAYING:
                        {
                                $this -> handlePlayingCommand ($channel, $nickname, $command, $parameters);
                                break;
                        }
                        
                        default:
                        {
                                throw new Exception (UnoMessage :: UNEXPECTED_CONDITION);
                        }
                }
        }
        
        /**
         * Handles commands which are available while the engine is idle.
         * 
         * @param string $channel The IRC channel the line appeared in.
         * @param string $nickname The person who typed this line.
         * @param string $command The main command.
         * @param string $parameters The parameters given to the command.
         */
        
        private function handleIdleCommand ($channel, $nickname, $command, $parameters)
        {
                if ($this -> mActiveChannel != null && $this -> mActiveChannel != strtolower($channel))
                {
                        return;
                }
                
                switch ($command)
                {
                        case UnoStrings :: COMMAND_JOIN_SHORT:
                        case UnoStrings :: COMMAND_JOIN_LONG:
                        {
                                $this -> mActiveChannel = strtolower($channel);
                                $this -> mEngine -> addPlayer ($nickname);
                                break;
                        }
                }
        }
        
        /**
         * Handles commands which are available while players are able to sign
         * up for the upcoming game.
         * 
         * @param string $channel The IRC channel the line appeared in.
         * @param string $nickname The person who typed this line.
         * @param string $command The main command.
         * @param string $parameters The parameters given to the command.
         */
        
        private function handlePendingCommand ($channel, $nickname, $command, $parameters)
        {
                if ($this -> mActiveChannel != null && $this -> mActiveChannel != strtolower($channel))
                {
                        return;
                }
                
                switch ($command)
                {
                        case UnoStrings :: COMMAND_JOIN_SHORT:
                        case UnoStrings :: COMMAND_JOIN_LONG:
                        {
                                if (!$this -> mEngine -> isPlayerAdded ($nickname))
                                {
                                        $this -> mEngine -> addPlayer ($nickname);
                                }
                                
                                break;
                        }
                        
                        case UnoStrings :: COMMAND_START_SHORT:
                        case UnoStrings :: COMMAND_START_LONG:
                        {
                                $this -> mEngine -> startGame ();
                                break;
                        }
                        
                        case $this -> mCommandPrefix . UnoStrings :: COMMAND_STOP:
                        {
                                $this -> mActiveChannel = null;
                                $this -> mEngine -> stopGame ();
                                break;
                        }
                }
        }
        
        /**
         * Handles commands which are available while a game of Uno is currently
         * underway.
         * 
         * @param string $channel The IRC channel the line appeared in.
         * @param string $nickname The person who typed this line.
         * @param string $command The main command.
         * @param string $parameters The parameters given to the command.
         */
        
        private function handlePlayingCommand ($channel, $nickname, $command, $parameters)
        {
                if ($this -> mActiveChannel != null && $this -> mActiveChannel != strtolower($channel))
                {
                        return;
                }
                
                switch ($command)
                {
                        case UnoStrings :: COMMAND_PLAY_SHORT:
                        case UnoStrings :: COMMAND_PLAY_LONG:
                        {
                                $this -> mEngine -> playCard ($nickname, $parameters);
                                break;
                        }
                        
                        case UnoStrings :: COMMAND_DRAW_SHORT:
                        case UnoStrings :: COMMAND_DRAW_LONG:
                        {
                                $this -> mEngine -> drawCard ($nickname);
                                break;
                        }
                        
                        case UnoStrings :: COMMAND_PASS_SHORT:
                        case UnoStrings :: COMMAND_PASS_LONG:
                        {
                                $this -> mEngine -> passTurn ($nickname);
                                break;
                        }
                        
                        case UnoStrings :: COMMAND_CARDS_LONG:
                        case UnoStrings :: COMMAND_CARDS_SHORT:
                        case $this -> mCommandPrefix . UnoStrings :: COMMAND_STATUS:
                        {
                                $this -> mEngine -> displayStatus ($nickname);
                                break;
                        }
                        
                        case $this -> mCommandPrefix . UnoStrings :: COMMAND_STOP:
                        {
                                $this -> mActiveChannel = null;
                                $this -> mEngine -> stopGame ();
                                break;
                        }
                        
                        case $this -> mCommandPrefix . UnoStrings :: COMMAND_SHUFFLE:
                        {
                                $this -> mEngine -> shuffleDeck ();
                        }
                }
        }
        
        /**
         * Parses the player's card input string to something a little more
         * understandable for the Uno engine. Throws Exceptions with specific
         * error messages in case of errors. These can be shown to the user.
         * 
         * @param string $input The card input that was received.
         * @return array
         * @todo Get rid of these clunky arrays, use UnoCard and some new to invent class.
         */
        
        public function parseCardInput ($input)
        {
                $input = trim (strtolower ($input));
                if ($input == '')
                {
                        throw new Exception (UnoStrings :: NO_CARD_SUPPLIED);
                }
                
                $chunks = preg_split ('/\s+/', $input);
                if (count ($chunks) == 1)
                {
                        // Single card, easy.
                        // Syntax: !play [color][card]
                        $color = $input [0];
                        $number = substr ($input, 1);
                        
                        if (!isset (self :: $sColorMap [$color], self :: $sNumberMap [$number]))
                        {
                                throw new Exception (UnoStrings :: INVALID_CARD_OR_COLOR_SUPPLIED);
                        }
                        
                        // Since we have only 1 parameter, we can dismiss this as invalid.
                        if (self :: $sColorMap [$color] == UnoEngine :: COLOR_WILDCARD)
                        {
                                throw new Exception (UnoStrings :: NEED_TO_SPECIFY_COLOR);
                        }
                        
                        return array
                        (
                                UnoEngine :: INDEX_INPUT  => UnoEngine :: INPUT_SINGLE,
                                UnoEngine :: INDEX_COLOR  => self :: $sColorMap [$color],
                                UnoEngine :: INDEX_NUMBER => self :: $sNumberMap [$number],
                                UnoEngine :: INDEX_NEXT   => null
                        );
                }
                else if (count ($chunks) == 2)
                {
                        // Syntax: !play w[card] [nextColor]
                        $color = $chunks [0] [0];
                        $number = (string) substr ($chunks [0], 1); // Casts false to "", which is CARD_NONE.
                        $nextColor = $chunks [1];
                        
                        if (!isset (self :: $sColorMap [$color], self :: $sNumberMap [$number], self :: $sColorMap [$nextColor]))
                        {
                                throw new Exception (UnoStrings :: INVALID_CARD_OR_COLOR_SUPPLIED);
                        }
                        
                        if (self :: $sColorMap [$color] != UnoEngine :: COLOR_WILDCARD)
                        {
                                throw new Exception (UnoStrings :: INVALID_CARD_OR_COLOR_SUPPLIED);
                        }
                        
                        return array
                        (
                                UnoEngine :: INDEX_INPUT  => UnoEngine :: INPUT_SINGLE,
                                UnoEngine :: INDEX_COLOR  => self :: $sColorMap [$color],
                                UnoEngine :: INDEX_NUMBER => self :: $sNumberMap [$number],
                                UnoEngine :: INDEX_NEXT   => self :: $sColorMap [$nextColor]
                        );
                }
                else if (count ($chunks) == 3 && ($chunks [0] == 's' || $chunks [0] == 'stack'))
                {
                        // Stack. Still easy. For us, at least. For the engine though...
                        // Syntax: !play s [card] [color]
                        $color = $chunks [2];
                        $number = $chunks [1];
                        
                        if (!isset (self :: $sColorMap [$color], self :: $sNumberMap [$number]))
                        {
                                throw new Exception (UnoStrings :: INVALID_CARD_OR_COLOR_SUPPLIED);
                        }
                        
                        return array
                        (
                                UnoEngine :: INDEX_INPUT  => UnoEngine :: INPUT_STACK,
                                UnoEngine :: INDEX_COLOR  => self :: $sColorMap [$color],
                                UnoEngine :: INDEX_NUMBER => self :: $sNumberMap [$number],
                                UnoEngine :: INDEX_NEXT   => null
                        );
                }
                else
                {
                        throw new Exception (UnoStrings :: INVALID_NOTATION_USED);
                }
        }
}